package base

import (
	"fmt"
	"io/ioutil"

	"topobuild/utils"

	"gopkg.in/yaml.v2"
)

/*
Links:
  -
    peer1:
      dev: "R001"
      port: "eth1"
    peer2:
      dev: "R101"
      port: "eth1"
    cidr: "192.168.1.0/24"
  -
    peer1:
      dev: "R002"
    peer2:
      dev: "R102"
  - simple: ["R003", "R103"]


asArea:
  -
    as: 100
    devInAs:
      - dev: "R001"
      - dev: "R002"
      - dev: "R003"
  -
    as: 200
    devInAs:
      - dev: "R101"
      - dev: "R102"
      - dev: "R103"
  -
    as: 300
    devInAs:
      - dev: "R301"
      - dev: "R302"
      - dev: "R303"
  -
    as: 400
      - dev: "R401"
      - dev: "R402"
      - dev: "R403"
  -
    as: -1
      - dev: "serv01"
      - dev: "serv02"
      - dev: "host01"
*/

// 解析上述yaml文件，生成如下结构体
type Peer struct {
	Dev    string `yaml:"dev,omitempty"`
	Port   string `yaml:"port,omitempty"`
	PortIp string `yaml:"portIp,omitempty"`
}

type Link struct {
	Peer1  Peer     `yaml:"peer1,omitempty"`
	Peer2  Peer     `yaml:"peer2,omitempty"`
	Cidr   string   `yaml:"cidr,omitempty"`
	Simple []string `yaml:"simple,omitempty"`
}

func (l *Link) AdjustLinkInfo() {
	if len(l.Simple) == 2 {
		l.Peer1 = Peer{Dev: l.Simple[0]}
		l.Peer2 = Peer{Dev: l.Simple[1]}
	}
}

type IntfDev struct {
	Port   string `yaml:"port,omitempty"`
	PortIp string `yaml:"portIp,omitempty"`
}

type DevInAs struct {
	Dev   string    `yaml:"dev,omitempty"`
	Intfs []IntfDev `yaml:"intfs,omitempty"`
}

type AsArea struct {
	As      int       `yaml:"as"`
	DevInAs []DevInAs `yaml:"devInAs"`
}

type Config struct {
	Links   []Link   `yaml:"Links"`
	AsAreas []AsArea `yaml:"asArea"`
}

func (c *Config) AdjustConfig_EthX() error {

	distriEthXToDev := func(dev DevInAs) error {
		epool := utils.NewEthPool()
		// 记录所有已经存在的ethX
		for _, id := range dev.Intfs {
			if id.Port != "" {
				epool.AddEth(id.Port)
			}
		}
		// 分配ethX
		epool.ReSetEth()
		for _, id := range dev.Intfs {
			epool.AddEth(id.Port)
		}
		for i, id := range dev.Intfs {
			if id.Port == "" {
				port := epool.NextEth()
				if port == "" {
					return fmt.Errorf("no ethX can be used")
				}
				dev.Intfs[i].Port = port
				epool.AddEth(port)
			}
		}
		return nil
	}

	for _, aa := range c.AsAreas {
		for _, dia := range aa.DevInAs {
			if err := distriEthXToDev(dia); err != nil {
				return err
			}
		}
	}
	return nil
}

func (c *Config) AdjustConfig_Distri() error {

	distriPeerToDev := func(peer Peer) bool {
		pdev := peer.Dev
		for i, aa := range c.AsAreas {
			for j, dia := range aa.DevInAs {
				if dia.Dev == pdev {
					c.AsAreas[i].DevInAs[j].Intfs = append(c.AsAreas[i].DevInAs[j].Intfs,
						IntfDev{
							Port:   peer.Port,
							PortIp: peer.PortIp,
						})
					return true
				}
			}
		}
		return false
	}

	for _, l := range c.Links {
		if !distriPeerToDev(l.Peer1) {
			return fmt.Errorf("peer1: %v not found in asArea", l.Peer1)
		}
		if !distriPeerToDev(l.Peer2) {
			return fmt.Errorf("peer2: %v not found in asArea", l.Peer2)
		}
	}

	return nil
}

func (c *Config) AdjustConfig_Links() error {
	cpm := utils.NewCidrPoolMask24()
	for i := range c.Links {
		// 填充不完整的Dev字段
		c.Links[i].AdjustLinkInfo()
		// 分配Cidr
		var cidr *utils.CidrMask24 = nil
		if c.Links[i].Cidr == "" {
			cidr = cpm.GetCidr()
		} else if !cpm.AddOneCidrMask(c.Links[i].Cidr) {
			cidr = cpm.GetCidr()
		} else {
			cm, _ := utils.ParseCidrMask(c.Links[i].Cidr)
			cidr = utils.NewCidrMask24FromCidrMask(cm)
		}
		c.Links[i].Cidr = cidr.String()
		// 使用cidr分配ip
		cidr.ReSetIp()
		for ip := cidr.NextIp(); ip != ""; ip = cidr.NextIp() {
			if ip == c.Links[i].Peer1.PortIp || ip == c.Links[i].Peer2.PortIp {
				continue
			}
			if c.Links[i].Peer1.PortIp == "" {
				c.Links[i].Peer1.PortIp = ip
				continue
			}
			if c.Links[i].Peer2.PortIp == "" {
				c.Links[i].Peer2.PortIp = ip
				continue
			}
			break
		}
	}
	return nil
}

func NewConfigFromFile(filename string) *Config {
	yamlFile, err := ioutil.ReadFile(filename)
	if err != nil {
		fmt.Println(err.Error())
		return nil
	}

	var config Config
	err = yaml.Unmarshal(yamlFile, &config)
	if err != nil {
		fmt.Println(err.Error())
		return nil
	}

	err = config.AdjustConfig_Links()
	if err != nil {
		fmt.Println(err.Error())
		return nil
	}
	err = config.AdjustConfig_Distri()
	if err != nil {
		fmt.Println(err.Error())
		return nil
	}
	err = config.AdjustConfig_EthX()
	if err != nil {
		fmt.Println(err.Error())
		return nil
	}

	// write the config to file topo-real.yaml
	configBytes, err := yaml.Marshal(config)
	if err != nil {
		fmt.Println(err.Error())
		return nil
	}
	err = ioutil.WriteFile("topo-real.yaml", configBytes, 0644)
	if err != nil {
		fmt.Println(err.Error())
		return nil
	}

	// fmt.Printf("config: %+v\n", config)
	return &config
}
